import cv2
import cvzone
import numpy as np
from cvzone.HandTrackingModule import HandDetector  # 导入手部检测模块
import math
 
# 构造一个贪吃蛇移动的类
class SnakeGameClass:
 
    #（一）初始化
    def __init__(self):
 
        self.points = []  # 蛇的身体的节点坐标
        self.lengths = []  # 蛇身各个节点之间的坐标
        self.currentLength = 0  # 当前蛇身长度
        self.allowedLength = 150  # 没吃东西时，蛇的总长度
        self.previousHead = (0,0)  # 前一个蛇头节点的坐标
 
    #（二）更新增加蛇身长度
    def update(self, imgMain, currentHead): # 输入图像，当前蛇头的坐标
 
        px, py = self.previousHead  # 获得前一个蛇头的x和y坐标
        cx, cy = currentHead  # 当前蛇头节点的x和y坐标
        
        # 添加当前蛇头的坐标到蛇身节点坐标列表中
        self.points.append([cx,cy])
 
        # 计算两个节点之间的距离
        distance = math.hypot(cx-px, cy-py)  # 计算平方和开根
        # 将节点之间的距离添加到蛇身节点距离列表中
        self.lengths.append(distance)
        # 增加当前蛇身长度
        self.currentLength += distance
 
        # 更新蛇头坐标
        self.previousHead = (cx,cy)
 
        #（三）减少蛇尾长度，即移动过程中蛇头到蛇尾的长度不大于150
        if self.currentLength > self.allowedLength:
 
            # 遍历所有的节点线段长度。新更新的蛇头索引在列表后面，蛇尾的索引在列表前面
            for i, length in enumerate(self.lengths):
 
                # 从蛇尾到蛇头依次减线段长度，得到的长度是否满足要求
                self.currentLength -= length
 
                # 从列表中删除蛇尾端的线段长度，以及蛇尾节点
                self.lengths.pop(i)
                self.points.pop(i)
 
                # 如果当前蛇身长度小于规定长度，满足要求，退出循环
                if self.currentLength < self.allowedLength:
                    break
 
        #（四）绘制蛇
        # 当节点列表中有值了，才能绘制
        if self.points:
 
            # 遍历蛇身节点坐标
            for i, point in enumerate(self.points):  
                # 绘制前后两个节点之间的连线
                if i != 0:
                    cv2.line(imgMain, tuple(self.points[i-1]), tuple(self.points[i]), (0,255,0), 20)
 
            # 在蛇头的位置画个圆
            cv2.circle(imgMain, tuple(self.points[-1]), 20, (255,0,0), cv2.FILLED)
 
        # 返回更新后的图像
        return imgMain
 
 
#（1）获取摄像头
cap = cv2.VideoCapture(0) # 0代表电脑自带的摄像头
# 设置显示窗口的size
cap.set(3, 1280)  # 窗口宽1280
cap.set(4, 720)   # 窗口高720
 
#（2）模型配置
detector = HandDetector(maxHands=1,  # 最多检测1只手
                        detectionCon=0.8)  # 最小检测置信度0.8
 
# 接收创建贪吃蛇的类
game = SnakeGameClass()
 
#（3）图像处理
while True:
 
    # 每次读取一帧相机图像，返回是否读取成功success，读取的帧图像img
    success, img = cap.read()
 
    # 图像翻转，使图像和自己呈镜像关系
    img = cv2.flip(img, 1)  # 0代表上下翻转，1代表左右翻转
 
    # 检测手部关键点。返回手部信息hands，绘制关键点后的图像img
    hands, img = detector.findHands(img, flipType=False)  # 由于上一行翻转过图像了，这里就不用翻转了
 
    # 查看关键点信息
    print(hands)
 
    #（4）关键点处理
    if hands:  # 如果检测到手了，那就处理关键点
 
        # 获得食指指尖坐标(x,y)
        hand = hands[0]  # 获取一只手的全部信息
        lmList = hand['lmList']  # 获得这只手的21个关键点的坐标(x,y,z)
        pointIndex = lmList[8][0:2]  # 只获取食指指尖关键点的（x,y）坐标
 
        # 更新贪吃蛇的节点，给出蛇头节点坐标。返回更新后的图像
        img = game.update(img, pointIndex)
 
    #（5）显示图像
    cv2.imshow('img', img)  # 输入图像显示窗口的名称及图像
     # 每帧滞留1毫秒后消失，并且按下ESC键退出
    if cv2.waitKey(1) & 0xFF == 27:
        break
 
# 释放视频资源
cap.release()
cv2.destroyAllWindows()